三十天很快要到了尾聲了,今天要來介紹 The Twelve-Factor App(下稱 12 Factor),它是開發 SaaS 的方法論,適用於 Web 或網路相關服務等軟體開發。怎麼突然討論起如何開發軟體呢?這跟 Docker 好像沒有什麼關係?可能有點奇怪,不過這個開發軟體的方法論,確實跟 Docker 有很大的關係。
筆者是先接觸 Docker 之後,再接觸 12 Factor 的。當時筆者還處在把 Docker 當輕量 Vagrant 用的時期,當時只感到阻礙重重。比較經典的例子像是:搞不懂為什麼 Apache container 一定要在 running Apache 的時候,才能透過 docker exec
進入 container。
後來在看 12 Factor 的時候,才發現錯把 container 當 VM 使用了。當時的感受是,原來 Docker 是實踐 12 Factor 的好例子;而反過來說,我們撰寫程式遵守了 12 Factor 方法,會讓使用 Docker 更順利。
今天帶讀者簡單了解 12 Factor 與 Docker 相關聯的地方,有興趣的讀者可以閱讀原文。
One codebase tracked in revision control, many deploys
一份原始碼,可以創造出多份部署。下圖是官方提供的示意圖:
來源:12 Factor
Docker 也有一樣的概念,若把 Dockerfile 作為原始碼,則相同的 Dockerfile 可以在不同環境建置並使用 docker run
執行;若把 image 作為原始碼又會更精簡--不同的環境都能直接使用 docker run
執行。
Explicitly declare and isolate dependencies
明確地聲明與隔離依賴。什麼是不明確地聲明?比方說,直接在環境安裝了全域都可以直接使用的套件,這就很容易踩到不明確聲明。
Dockerfile 的流程中,必須明確寫出安裝依賴的過程,才有辦法正常執行應用程式,如 Laravel image 的範例 Dockerfile:
# 安裝 bcmath 與 redis
RUN docker-php-ext-install bcmath
RUN pecl install redis
RUN docker-php-ext-enable redis
# 安裝程式依賴套件
COPY composer.* ./
RUN composer install --no-dev --no-scripts && composer clear-cache
有了明確聲明依賴後,任何人都可以依照這個流程打造出一模一樣的環境--執行 docker build。
Store config in the environment
把設定存放在環境裡。設定包含下面幾種:
不同環境的設定可能差異非常大,但它們都可以在同一份原始碼上正常運作。這樣的做法有幾個好處:
MySQL environment 的範例正是綜合了 I. Codebase 方法與 III. Config 來達成同一份原始碼,不同設定的部署實例。
Treat backing services as attached resources
後端服務作為附加資源。後端服務包括了 MySQL、Redis 等常見的第三方服務,當然也包括了我們依本篇文章設計的 12 Factor App。
來源:12 Factor
這樣設計的好處即容易替換服務,比方說想把 MySQL 換成 MariaDB,或是 Redis 3.0 升級成 Redis 5,只要把連線設定調整即可。
若使用 Docker Compose 會更方便,只要更新完定義檔,重新執行啟動指令就可以立即替換了:
docker-composer up -d
Strictly separate build and run stages
嚴格區分 build 和 run。build 階段才能調整程式與整合依賴成 artifacts;run 階段則非常單純,把 artifacts 拿來執行就行了。
來源:12 Factor
在 Docker container 修改程式碼,其實是非常麻煩的,要做非常多前置準備。但如果遵守 build / run 分離的方法,就會變得非常簡單。
Execute the app as one or more stateless processes
使用一個以上的無狀態 process 來執行應用程式。這裡的關鍵在於要設計成無狀態,如果有狀態或資料要保存,可以透過 IV. Backing services 的資料庫保存。
Docker 每次啟動 container 都是全新的沒有過去狀態的,之前提到的一次性。換句話說,設計一個能在 Docker 上運作良好的 container,就代表有符合此方法--Container 應用正是活動此特性的範例參考。
Export services via port binding
透過 port 綁定來提供服務。
Docker 可以用 port forwarding 來對外提供服務,Dockerfile 則可以使用 EXPOSE
指令讓 container 之間也可以互相使用 port 存取服務
EXPOSE 80
CMD ["php", "artisan", "serve", "--port", "80"]
Scale out via the process model
透過 process model 做水平擴展。12 Factor 是以工作類型來分類 process,如 HTTP 請求給 web process 處理,背景則是使用 worker process。
來源:12 Factor
配合 VI. Processes 提到的無狀態特性,可以讓應用程式非常容易做水平擴展,甚至是跨 VM、跨實體機器的擴展。
Container 即 process,因此 Docker 要做水平擴展是非常簡單的--多跑幾次 docker run
就行了,甚至 Docker Compose 還提供專用的 scale 指令:
docker-compose scale web=2 worker=3
Maximize robustness with fast startup and graceful shutdown
快速啟動,優雅終止,最大化系統的強健性。
將應用程式設計成可以快速啟動並開放服務,好處就在於擴展和上線變得非常容易。收到終止信號 SIGTERM
並優雅終止,則要求 process 停止接收任務,並把最的任務完成後,才真正結束 process。
Docker 在管理啟動或終止都做的很完整,主要還是程式設計要得當。
Keep development, staging, and production as similar as possible
盡可能保持環境一致,環境一致最大的好處還是在於開發除錯的效率。「我的電腦上就沒問題」這句話正是這個方法的反指標,正因個人環境上有做了特別安裝,才讓程式有辦法正常運作,這個安裝過程就得考慮是否要同步到其他人或測試環境上。
Dockerfile 是一個 IoC 很好的實踐,因此非常容易做到環境一致。
Treat logs as event streams
使用 event stream 輸出 log。正如 IV. Backing services 與 VI. Processes 所提到的,12 Factor App 不應該保存狀態,類似的,它也不應該保存 log,而是要把 log 作為 event stream 輸出。
Docker 可以截取 process 的標準輸出(STDOUT
),並透過內部機制轉到 log driver,因此程式只要處理好標準輸出即可。
Run admin/management tasks as one-off processes
管理與維護任務作為一次性的 process 執行,像 migration 正是屬於這一類的任務。
對 Docker 而言,要在已啟動的 container 上執行 process 太簡單了,使用 docker exec
即可達成任務。
docker exec -it web php artisan migrate
最後回頭來看 12 Factor 當初設計的目標:
這段原文很長,但因為是必要的,所以還是無斷複製過來
- Use declarative formats for setup automation, to minimize time and cost for new developers joining the project;
- Have a clean contract with the underlying operating system, offering maximum portability between execution environments;
- Are suitable for deployment on modern cloud platforms, obviating the need for servers and systems administration;
- Minimize divergence between development and production, enabling continuous deployment for maximum agility;
- And can scale up without significant changes to tooling, architecture, or development practices.
簡單整理如下:
曾有人問筆者:身為一個開發者,學完 run container,學完 build image,之後要學什麼呢?
學習寫出符合 12 Factor 的程式。
能在 Docker 上運作良好的,正是符合 12 Factor 的應用程式。